//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<uniscribe.cpp>
///	@path	~/src/gui/text/
///	@date	2007/12/06
///	@desc	Uniscribe.

#include "config/config.h"

// uniscribe
#include <Unknwn.h>
#include <usp10.h>
#include <dimm.h>

#include "lib/utilities/debug.h"

#include "gui/style/uniscribe.h"

namespace xeres {

	namespace
	{
		/*!
			\class	_Uniscribe
			\brief	Proxy class for uniscribe.
		*/
		class _Uniscribe
		{
		public:

			// instance
			static _Uniscribe& instance( void )
			{
				static _Uniscribe s_uniscribeInstance;
				return s_uniscribeInstance;
			}

		//@ proxy

			HRESULT ScriptApplyDigitSubstitution(
				const SCRIPT_DIGITSUBSTITUTE * psds , SCRIPT_CONTROL * psc , SCRIPT_STATE * pss )
			{
				if( _ScriptApplyDigitSubstitution )
					return _ScriptApplyDigitSubstitution( psds , psc , pss );
				else
					return E_NOTIMPL;
			}
			HRESULT ScriptStringAnalyse( HDC hdc,
				const void *pString,
				int cString,
				int cGlyphs,
				int iCharset,
				DWORD dwFlags,
				int iReqWidth,
				SCRIPT_CONTROL *psControl,
				SCRIPT_STATE *psState,
				const int *piDx,
				SCRIPT_TABDEF *pTabdef,
				const BYTE *pbInClass,
				SCRIPT_STRING_ANALYSIS *pssa )
			{
				if( _ScriptStringAnalyse )
					return _ScriptStringAnalyse( hdc , pString , cString , cGlyphs , iCharset , dwFlags , iReqWidth ,
						psControl , psState , piDx , pTabdef , pbInClass , pssa );
				else
					return E_NOTIMPL;
			}
 			HRESULT ScriptStringCPtoX( SCRIPT_STRING_ANALYSIS ssa , int icp , BOOL fTrailing , int *pX )
			{
				if( _ScriptStringCPtoX )
					return _ScriptStringCPtoX( ssa , icp , fTrailing , pX );
				else
					return E_NOTIMPL;
			}
			HRESULT ScriptStringXtoCP( SCRIPT_STRING_ANALYSIS ssa , int iX , int * piCh , int * piTrailing )
			{
				if( _ScriptStringXtoCP )
					return _ScriptStringXtoCP( ssa , iX , piCh , piTrailing );
				else
					return E_NOTIMPL;
			}
			HRESULT ScriptStringFree( SCRIPT_STRING_ANALYSIS * pssa )
			{
				if( _ScriptStringFree )
					return _ScriptStringFree( pssa );
				else 
					return E_NOTIMPL;
			}
			const SCRIPT_LOGATTR * ScriptString_pLogAttr( SCRIPT_STRING_ANALYSIS ssa )
			{
				if( _ScriptString_pLogAttr )
					return _ScriptString_pLogAttr( ssa );
				else
					return NULL;
			}
			const int * ScriptString_pcOutChars( SCRIPT_STRING_ANALYSIS ssa )
			{
				if( _ScriptString_pcOutChars )
					return _ScriptString_pcOutChars( ssa );
				else
					return NULL;
			}

		private:

			//@ ctor
			_Uniscribe( void )
			{
				static const wchar_t * UNISCRIBE_DLLNAME = _S("\\usp10.dll");

				// load uniscribe module

				WCHAR wszPath[ OS_MAX_PATH ];
				if( !::GetSystemDirectory( wszPath, OS_MAX_PATH ) )
				{
					__win32_error( _S("_Uniscribe::CTOR: Failed to get system dir: '%s'.") );
					FATAL_ABORT( _S("Failed to get system directory.") );
				}

				WString path = wszPath + WString( UNISCRIBE_DLLNAME );

				s_hDll = ::LoadLibrary( path.c_str() );
				if( s_hDll == NULL )
				{
					TRACE_ERROR( _S("_Uniscribe::CTOR: Failed to get <%s>.") , UNISCRIBE_DLLNAME );
					FATAL_ABORT( _S("Missing uniscribe library.") );
				}

#define GETPROCADDRESS( APIName ) \
	_##APIName = NULL; \
	Temp = ::GetProcAddress( s_hDll , #APIName ); \
	if( Temp ) \
		*(FARPROC*)&_##APIName = Temp; \
	else XS_ASSERT( false );

				FARPROC Temp;
				GETPROCADDRESS( ScriptApplyDigitSubstitution );
				GETPROCADDRESS( ScriptStringAnalyse );
				GETPROCADDRESS( ScriptStringCPtoX );
				GETPROCADDRESS( ScriptStringXtoCP );
				GETPROCADDRESS( ScriptStringFree );
				GETPROCADDRESS( ScriptString_pLogAttr );
				GETPROCADDRESS( ScriptString_pcOutChars );

#undef GETPROCADDRESS
			}

			//@ dtor
			~_Uniscribe( void )
			{
				::FreeLibrary( s_hDll );
				s_hDll = NULL;
			}

		//@ data

			// Uniscribe DLL handle
			HINSTANCE s_hDll;

			// Function pointers
			HRESULT (WINAPI *_ScriptApplyDigitSubstitution)( const SCRIPT_DIGITSUBSTITUTE*, SCRIPT_CONTROL*, SCRIPT_STATE* );
			HRESULT (WINAPI *_ScriptStringAnalyse)( HDC, const void *, int, int, int, DWORD, int, SCRIPT_CONTROL*, SCRIPT_STATE*, const int*, SCRIPT_TABDEF*, const BYTE*, SCRIPT_STRING_ANALYSIS* );
			HRESULT (WINAPI *_ScriptStringCPtoX)( SCRIPT_STRING_ANALYSIS, int, BOOL, int* );
			HRESULT (WINAPI *_ScriptStringXtoCP)( SCRIPT_STRING_ANALYSIS, int, int*, int* );
			HRESULT (WINAPI *_ScriptStringFree)( SCRIPT_STRING_ANALYSIS* );
			const SCRIPT_LOGATTR* (WINAPI *_ScriptString_pLogAttr)( SCRIPT_STRING_ANALYSIS );
			const int* (WINAPI *_ScriptString_pcOutChars)( SCRIPT_STRING_ANALYSIS );
		};
	}

	// Uniscribe
	Uniscribe::Uniscribe( HFONT font )
	{
		m_analysis = NULL;
		m_analyseRequired = true;
		m_isPassword = false;
 		if( font != NULL )
 		{
 			LOGFONT lf;
 			::GetObject( font , sizeof(LOGFONT) , &lf );
 			m_font = ::CreateFontIndirect( &lf );
 		}
		m_font = font;
	}

	// ~Uniscribe
	Uniscribe::~Uniscribe( void )
	{
		if( m_analysis )
			_Uniscribe::instance().ScriptStringFree( &m_analysis );

		ClearFont();
	}

	// operator[]
	wchar_t& Uniscribe::operator[]( int index )
	{
		// This version of operator[] is called only
		// if we are asking for write access, so
		// re-analysis is required.
		m_analyseRequired = true;
		return m_buffer[index];
	}

	// operator[]
	wchar_t Uniscribe::operator[] ( int index ) const
	{
		return m_buffer[index];
	}

	// Clear
	void Uniscribe::Clear( void )
	{
		m_buffer.clear();
		m_analyseRequired = true;
	}

	// InsertChar
	bool Uniscribe::InsertChar( int index , wchar_t wchar )
	{
		assert( index >= 0 );

		if( index < 0 || index > (int)m_buffer.size() )
		{
			// invalid index
			return false;
		}

		m_buffer.insert( index , 1 , wchar );
		m_analyseRequired = true;

		return true;
	}

	// RemoveChar
	bool Uniscribe::RemoveChar( int index )
	{
		if( ! m_buffer.size() || index < 0 || index >= (int)m_buffer.size() )
			return false;  // Invalid index

		m_buffer.erase( index , 1 );
		m_analyseRequired = true;
		return true;
	}

	// InsertString
	bool Uniscribe::InsertString( int index , const WString& str )
	{
		assert( index >= 0 );

		if( index > (int)m_buffer.size() )
			return false; // invalid index

		m_buffer.insert( index , str );
		m_analyseRequired = true;
		return true;
	}

	// SetText
	bool Uniscribe::SetText( const WString& text )
	{
		m_buffer = text;
		m_analyseRequired = true;
		return true;
	}

	// CPtoX
	bool Uniscribe::CPtoX( int nCP , BOOL bTrail , int * pX )
	{
		// Default
		assert( pX );
		*pX = 0;

		HRESULT hr = S_OK;
		if( m_analyseRequired )
			hr = InternalAnalyze();

		if( SUCCEEDED( hr ) )
			hr = _Uniscribe::instance().ScriptStringCPtoX( m_analysis , nCP , bTrail , pX );

		return SUCCEEDED( hr );
	}

	// XtoCP
	bool Uniscribe::XtoCP( int nX , int * pCP , int * pnTrail )
	{
		// Default
		assert( pCP && pnTrail );
		*pCP = 0;
		*pnTrail = FALSE;

		HRESULT hr = S_OK;
		if( m_analyseRequired )
			hr = InternalAnalyze();

		if( SUCCEEDED( hr ) )
			hr = _Uniscribe::instance().ScriptStringXtoCP( m_analysis , nX , pCP , pnTrail );

		// If the coordinate falls outside the text region, we
		// can get character positions that don't exist.  We must
		// filter them here and convert them to those that do exist.
		if( *pCP == -1 && *pnTrail == TRUE )
		{
			*pCP = 0;
			*pnTrail = FALSE;
		}
		else if( *pCP > (int)m_buffer.size() && *pnTrail == FALSE )
		{
			*pCP = (int)m_buffer.size();
			*pnTrail = TRUE;
		}
		
		return SUCCEEDED( hr );
	}

	// GetPriorItemPos
	void Uniscribe::GetPriorItemPos( int nCP , int * pPrior )
	{
		// Default is the char itself
		*pPrior = nCP;

		if( m_analyseRequired )
			if( FAILED( InternalAnalyze() ) )
				return;

		const SCRIPT_LOGATTR *pLogAttr = _Uniscribe::instance().ScriptString_pLogAttr( m_analysis );
		if( !pLogAttr )
			return;

		if( ! _Uniscribe::instance().ScriptString_pcOutChars( m_analysis ) )
			return;
		int nInitial = *_Uniscribe::instance().ScriptString_pcOutChars( m_analysis );
		if( nCP - 1 < nInitial )
			nInitial = nCP - 1;
		for( int i = nInitial; i > 0; --i )
		{
			if( pLogAttr[i].fWordStop ||       // Either the fWordStop flag is set
				( !pLogAttr[i].fWhiteSpace &&  // Or the previous char is whitespace but this isn't.
				pLogAttr[i-1].fWhiteSpace ) )
			{
				*pPrior = i;
				return;
			}
			// We have reached index 0.  0 is always a break point, so simply return it.
		}
		*pPrior = 0;
	}

	// GetNextItemPos
	void Uniscribe::GetNextItemPos( int nCP , int * pPrior )
	{
		*pPrior = nCP;  // Default is the char itself

		HRESULT hr = S_OK;
		if( m_analyseRequired )
			hr = InternalAnalyze();
		if( FAILED( hr ) )
			return;

		const SCRIPT_LOGATTR *pLogAttr = _Uniscribe::instance().ScriptString_pLogAttr( m_analysis );
		if( !pLogAttr )
			return;

		if( !_Uniscribe::instance().ScriptString_pcOutChars( m_analysis ) )
			return;
		int nInitial = *_Uniscribe::instance().ScriptString_pcOutChars( m_analysis );

		if( nCP + 1 < nInitial )
			nInitial = nCP + 1;

		int i = nInitial;
		int limit = *_Uniscribe::instance().ScriptString_pcOutChars( m_analysis );
		while( limit > 0 && i < limit - 1 )
		{
			if( pLogAttr[i].fWordStop )      // Either the fWordStop flag is set
			{
				*pPrior = i;
				return;
			}
			else
			if( pLogAttr[i].fWhiteSpace &&  // Or this whitespace but the next char isn't.
				!pLogAttr[i+1].fWhiteSpace )
			{
				*pPrior = i+1;  // The next char is a word stop
				return;
			}

			++i;
			limit = *_Uniscribe::instance().ScriptString_pcOutChars( m_analysis );
		}
		// We have reached the end. It's always a word stop, so simply return it.
		*pPrior = *_Uniscribe::instance().ScriptString_pcOutChars( m_analysis ) - 1;
	}

	// Analyze
	bool Uniscribe::Analyze( void )
	{
		return InternalAnalyze();
	}

	// SetFont
	void Uniscribe::SetFont( HFONT font )
	{
		ClearFont();
		if( font != NULL )
		{
			LOGFONT lf;
			::GetObject( font , sizeof(LOGFONT) , &lf );
			m_font = ::CreateFontIndirect( &lf );
		}
		m_font = font;
	}

	// ClearFont
	void Uniscribe::ClearFont( void )
	{
 		if( m_font != NULL )
 			::DeleteObject( m_font );
		m_font = NULL;
	}

	// InternalAnalyze
	bool Uniscribe::InternalAnalyze( void )
	{
		XS_ASSERT( m_font != NULL );

		if( m_analysis )
			_Uniscribe::instance().ScriptStringFree( &m_analysis );
		assert( m_analysis == NULL );

		SCRIPT_CONTROL ScriptControl; // For uniscribe
		SCRIPT_STATE ScriptState;   // For uniscribe
		::ZeroMemory( &ScriptControl, sizeof(ScriptControl) );
		::ZeroMemory( &ScriptState, sizeof(ScriptState) );
		_Uniscribe::instance().ScriptApplyDigitSubstitution( NULL, &ScriptControl, &ScriptState );

		size_t len = m_buffer.size();

		HDC dc = ::GetWindowDC( NULL );
		HFONT oldFont = (HFONT)::SelectObject( dc , m_font );
		int tabStops [] = { 4 };
		SCRIPT_TABDEF tab = { 1 , 0 , tabStops , 0 };

		HRESULT hr = _Uniscribe::instance().ScriptStringAnalyse(
			dc ,
			m_buffer.c_str() ,
			(int)len + 1,  // NULL is also analyzed.
			(int)(len * 1.5) + 16 ,
			-1,
			SSA_BREAK | SSA_GLYPHS | SSA_FALLBACK | SSA_LINK | SSA_TAB ,
			0,
			&ScriptControl,
			&ScriptState,
			NULL,
			&tab,
			NULL,
			&m_analysis );

		::SelectObject( dc , oldFont );
		::ReleaseDC( NULL , dc );

		if( SUCCEEDED( hr ) )
		{
			m_analyseRequired = false;  // Analysis is up-to-date
		}
		else
		{
			FATAL_ABORT( _S("Failed to analyze string.") );
		}

		return true;
	}

} // namespace xeres
